#include "fcgi_config.h"

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "fcgiapp.h"


#define FORK_COUNT 12//129  /* (128 + 1) */

static int listen_fd = -1;

typedef struct fork_info
{
    pid_t pid;
    time_t birth;
    FCGX_Request request;
} fork_info_s, *fork_info_p;

fork_info_s fork_info_array[FORK_COUNT];
    

static void
init_fork_array(void)
{
    int i;
    for (i = 0; i < FORK_COUNT; ++i) {
        fork_info_array[i].pid = -1;
        fork_info_array[i].birth = 0;
        FCGX_InitRequest(&fork_info_array[i].request, listen_fd, 0);
    }

    fork_info_array[0].pid = 0;
}

static fork_info_s *
get_empty_fork(void)
{
    int i;
    if ((int)fork_info_array[0].pid >= (FORK_COUNT - 1)) {
        return NULL;
    }

    for (i = 1; i < FORK_COUNT; ++i) {
        if (fork_info_array[i].pid == -1) {
            ++fork_info_array[0].pid;
            return &fork_info_array[i];
        }
    }
    return NULL;
}

static void
free_fork_by_pid(pid_t pid)
{
    int i;
    for (i = 1; i < FORK_COUNT; ++i) {
        if (fork_info_array[i].pid == pid) {
            fork_info_array[i].pid = -1;
            fork_info_array[i].birth = 0;
            //FCGX_Finish_r(&fork_info_array[i].request);
            
            if (--fork_info_array[0].pid < 0) {
                fork_info_array[0].pid = 0;
            }
            break;
        }
    }
}

static void
free_fork_by_ptr(fork_info_s *fork_info)
{
    fork_info->pid = -1;
    fork_info->birth = 0;

    if (--fork_info_array[0].pid < 0) {
        fork_info_array[0].pid = 0;
    }
}

static void
check_fork_timeout(void)
{
#define TIMEOUT_TIME  15//(60 * 10)  /* 10 minutes */
    int i;
    pid_t pid;
    time_t now = time(NULL);

    for (i = 1; i < FORK_COUNT; ++i) {
        if ((fork_info_array[i].pid > 0) && 
            ((int)(now - fork_info_array[i].birth) > TIMEOUT_TIME)) 
        {
            printf("ERR: pid %d time out, kill it!\n", fork_info_array[i].pid);
            pid = fork_info_array[i].pid;
            free_fork_by_ptr(&fork_info_array[i]);

            kill(pid, SIGUSR1);
        }
    }
}

static void
dump_fork_array(void)
{
    int i, cnt = 0;

    printf("\n");
    for (i = 1; i < FORK_COUNT; ++i) {
        if (fork_info_array[i].pid > 0) {
            printf("[%d] pid: %d, birth: %d ", 
                    ++cnt,
                    fork_info_array[i].pid,
                    (int)fork_info_array[i].birth);
        }
    }
    printf("\n=====================\ntotal run fork: %d\n", cnt);
    printf("\n");
}

void 
sig_child_cb(int signo)
{
    switch(signo)
    {
        case SIGUSR1:
            printf("Signal SIGUSR1 received! %s\n", strsignal(SIGUSR1));
            exit(0);
            break;

        case SIGTERM:
            printf("Signal SIGTERM received! %s\n", strsignal(SIGTERM));
            break;

        default:
            printf("other signal received!\n");
    }
}

static void *
doit(FCGX_Request *request)
{
    pid_t pid = getpid();
    char *server_name;
        
    printf("%s %d %d\n", __FUNCTION__, __LINE__, pid);

    signal(SIGUSR1, sig_child_cb);

    server_name = FCGX_GetParam("SERVER_NAME", request->envp);

    FCGX_FPrintF(request->out,
            "Content-type: text/html\r\n"
            "\r\n"
            "<title>FastCGI Hello! (multi-threaded C, fcgiapp library)</title>"
            "<h1>FastCGI Hello! (multi-threaded C, fcgiapp library)</h1>"
            "Thread %d, Process %ld<p>"
            "Request counts for %d threads running on host <i>%s</i><p><code>",
            81, pid, FORK_COUNT, server_name ? server_name : "?");

    FCGX_Finish_r(request);

    sleep(30);
    exit(0);
}

void 
sig_father_cb(int signo)
{
    int pid = -1;
    int status;
    
    printf("%s %d %d\n", __FUNCTION__, __LINE__, pid);
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        printf("%s %d %d\n", __FUNCTION__, __LINE__, pid);
        free_fork_by_pid(pid);
    }
        
    check_fork_timeout();
    dump_fork_array();
}

int 
main(int argc, char **argv)
{
    int i, rc;
    pid_t pid = -1;
    int status;
    fork_info_s *fork_info = NULL;

    FCGX_Init();

    listen_fd = FCGX_OpenSocket("127.0.0.1:9000", 128);
    if (listen_fd < 0) {
        printf("listen fd error!\n");
        exit(-1);
    }
    
    init_fork_array();

    signal(SIGCHLD, sig_father_cb);

    for (;;) {
        pid = -1;
        //printf("%s %d %d\n", __FUNCTION__, __LINE__, pid);

        if ((fork_info = get_empty_fork()) == NULL) {
            printf("ERR: fork array is full!\n");
            check_fork_timeout();
            sleep(5);
            continue;
        }
        
        rc = FCGX_Accept_r(&fork_info->request);
        if (rc < 0) {
            printf("ERR: FCGI accept error[%d %s]!\n", rc, strerror(0-rc));
            exit(-1);
        }

        fork_info->birth = time(NULL);
        if ((pid = fork()) < 0) {
            printf("ERR: fork error!\n");
            free_fork_by_ptr(fork_info);
            continue;
        } else if (pid == 0) { /* child process */
            doit(&fork_info->request);
        }

        /* father process */
        fork_info->pid = pid;
    }

    exit(0);
}

